package org.eweb4j.mvc.action;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;

import org.eweb4j.cache.ActionConfigBeanCache;
import org.eweb4j.cache.SingleBeanCache;
import org.eweb4j.config.LogFactory;
import org.eweb4j.ioc.IOC;
import org.eweb4j.mvc.Context;
import org.eweb4j.mvc.MIMEType;
import org.eweb4j.mvc.ParamUtil;
import org.eweb4j.mvc.action.annotation.DateFormat;
import org.eweb4j.mvc.action.annotation.Ioc;
import org.eweb4j.mvc.action.annotation.Singleton;
import org.eweb4j.mvc.config.ActionClassCache;
import org.eweb4j.mvc.config.MVCConfigConstant;
import org.eweb4j.mvc.config.bean.ActionConfigBean;
import org.eweb4j.mvc.config.bean.ResultConfigBean;
import org.eweb4j.mvc.interceptor.InterExecution;
import org.eweb4j.mvc.validator.ValidateExecution;
import org.eweb4j.util.ClassUtil;
import org.eweb4j.util.JsonConverter;
import org.eweb4j.util.ReflectUtil;
import org.eweb4j.util.StringUtil;

/**
 * Action 执行器
 * 
 * @author weiwei
 * @since 1.b.8
 * 
 */
public class ActionExecution {

	private Context context;

	private Object actionObject;
	private Class<?> clazz;
	private Object retn;

	private ReflectUtil ru;

	// 验证器
	private boolean handleValidator() throws Exception {

		Map<String, String> error = ValidateExecution.checkValidate(context
				.getMvcBean().getValidator(), context.getQueryParamMap(),
				context.getRequest());

		if (error != null && !error.isEmpty()) {
			ValidateExecution.showValidateError(context.getMvcBean()
					.getShowValErrorType(), error, context.getRequest(),
					context.getResponse());

			StringBuilder sb = new StringBuilder();
			sb.append("MVC:验证器发现了错误").append(error);

			LogFactory.getMVCLogger("INFO").write(sb.toString());
			return false;
		}

		return true;
	}

	public ActionExecution(String uri, String httpMethod, Context context) {
		this.context = context;
		this.context.setUri(uri);
		this.context.setHttpMethod(httpMethod);
		this.context.setQueryParamMap(new HashMap<String, String[]>());
		this.context.setPathParamMap(new HashMap<String, String[]>());
	}

	public boolean findAction() throws Exception {
		// URL参数
		Map<String, List<?>> pathParams = null;
		if (ActionConfigBeanCache.containsKey(this.context.getUri())
				|| (pathParams = ActionConfigBeanCache.getByMatches(
						this.context.getUri(), this.context.getHttpMethod())) != null) {

			// 处理形如" /xxx/{id}/{name} "的URI
			if (pathParams != null && pathParams.containsKey("mvcBean")) {
				// 根据Url配置的UrlParam获取参数值
				this.context.setMvcBean((ActionConfigBean) pathParams.get(
						"mvcBean").get(0));

				this.context.getPathParamMap().putAll(
						ParamUtil.getPathParamMap(pathParams));
				this.context.getQueryParamMap().putAll(
						this.context.getPathParamMap());
			} else
				this.context.setMvcBean(ActionConfigBeanCache.get(this.context
						.getUri()));

			// 将request的请求参数转到另外一个map中去
			this.context.getQueryParamMap().putAll(
					ParamUtil.copyReqParams(this.context.getRequest()));

			if (this.context.getMvcBean() != null)
				return true;
		}

		return false;
	}

	private Object initPojo() throws Exception {
		clazz = ActionClassCache.get(this.context.getMvcBean().getClazz());
		Annotation singletonAnn = clazz.getAnnotation(Singleton.class);
		if (singletonAnn != null) {
			this.actionObject = SingleBeanCache.get(clazz);
			if (this.actionObject == null) {
				this.actionObject = clazz.newInstance();
				SingleBeanCache.add(clazz, this.actionObject);
			}
		} else
			this.actionObject = clazz.newInstance();

		ru = new ReflectUtil(this.actionObject);

		return this.actionObject;
	}

	// IOC，注入对象到pojo
	private void injectIocBean() throws Exception {
		Field[] fields = ru.getFields();
		if (fields == null)
			return;

		for (Field f : fields) {
			Class<?> type = f.getType();
			Ioc ioc = f.getAnnotation(Ioc.class);
			if (ioc == null)
				continue;
			String beanId = "";
			if (ioc.value().trim().length() == 0)
				beanId = type.getSimpleName();
			else
				beanId = StringUtil.parsePropValue(ioc.value());

			Method setter = ru.getSetter(f.getName());
			if (setter == null)
				continue;

			setter.invoke(this.actionObject, IOC.getBean(beanId));
		}
	}

	private void exeActionLog() {
		StringBuilder sb = new StringBuilder();
		sb.append("MVC:执行")
				.append(this.context.getUri() + "@"
						+ this.context.getHttpMethod()).append("[Action];");
		LogFactory.getMVCLogger("INFO").write(sb.toString());
	}

	private Object[] assemParams(Class<?>[] paramTypes, Annotation[][] paramAnns)
			throws Exception {
		Object[] params = new Object[paramTypes.length];
		for (int i = 0; i < paramTypes.length; ++i) {
			Annotation[] anns = paramAnns[i];
			Class<?> paramClass = paramTypes[i];
			String[] paramValue = null;

			// ------------------------------------------------------
			// 通过给定class 获取对应的ActionContextObj
			if (HttpServletRequest.class.isAssignableFrom(paramClass)) {
				params[i] = this.context.getRequest();
				continue;
			}

			if (HttpServletResponse.class.isAssignableFrom(paramClass)) {
				params[i] = this.context.getResponse();
				continue;
			}

			if (PrintWriter.class.isAssignableFrom(paramClass)) {
				params[i] = this.context.getWriter();
				continue;
			}

			if (ServletOutputStream.class.isAssignableFrom(paramClass)) {
				params[i] = this.context.getOut();
				continue;
			}

			if (HttpSession.class.isAssignableFrom(paramClass)) {
				params[i] = this.context.getSession();
				continue;
			}

			if (ActionProp.class.isAssignableFrom(paramClass)) {
				if (this.context.getActionProp() == null)
					this.context.setActionProp(new ActionProp(clazz.getName()));

				params[i] = this.context.getActionProp();
				continue;
			}

			if (QueryParams.class.isAssignableFrom(paramClass)) {
				params[i] = this.context.getQueryParams();
				continue;
			}

			if (ClassUtil.isPojo(paramClass)) {
				params[i] = this.injectParam2Pojo(paramClass);
				continue;
			}

			PathParam pathParamAnn = this.getPathParamAnn(anns);
			if (pathParamAnn != null) {
				paramValue = this.getPathParamValue(pathParamAnn);
				params[i] = ClassUtil.getParamVal(paramClass, paramValue[0]);
				continue;
			}

			QueryParam queryParamAnn = this.getQueryParamAnn(anns);
			if (queryParamAnn == null)
				continue;

			paramValue = this.getQueryParamValue(queryParamAnn);

			if (java.util.Date.class.isAssignableFrom(paramClass)) {
				params[i] = this.getDateParam(anns, paramValue[0]);
				continue;
			}

			if (paramClass.isArray())
				params[i] = ClassUtil.getParamVals(paramClass, paramValue);
			else
				params[i] = ClassUtil.getParamVal(paramClass, paramValue[0]);
		}

		return params;
	}

	private Method getFirstMethd(Method[] methods) {
		Method m = methods[0];
		if (methods.length == 1)
			return m;

		// 如果含有两个或以上同名的方法,优先拿到被@Path注解的第一个方法
		for (Method mm : methods) {
			Path p = mm.getAnnotation(Path.class);
			if (p == null)
				continue;

			m = mm;
			break;
		}

		return m;
	}

	private String[] getQueryParamValue(QueryParam paramAnn) {
		String[] paramValue = this.context.getQueryParamMap().get(
				paramAnn.value());

		return getParamValue(paramValue);
	}

	private String[] getPathParamValue(PathParam paramAnn) {
		String[] paramValue = this.context.getPathParamMap().get(
				paramAnn.value());

		return getParamValue(paramValue);
	}

	private String[] getParamValue(String[] paramValue) {
		if (paramValue == null || paramValue.length == 0
				|| paramValue[0] == null) {
			paramValue = new String[] { "" };
		}

		return paramValue;
	}

	private Date getDateParam(Annotation[] anns, String paramValue)
			throws Exception {
		DateFormat dateAnn = null;
		for (Annotation a : anns) {
			if (a == null)
				continue;

			if (!a.annotationType().isAssignableFrom(DateFormat.class))
				continue;

			dateAnn = (DateFormat) a;
			break;
		}

		String pattern = "yyyy-MM-dd HH:mm:ss";
		if (dateAnn != null)
			pattern = dateAnn.value();

		SimpleDateFormat sdf = new SimpleDateFormat(pattern);
		return sdf.parse(paramValue);
	}

	private Object injectParam2Pojo(Class<?> paramClass) throws Exception {
		Object paramObj = paramClass.newInstance();
		this.injectActionCxt2Pojo(paramObj);
		// 注入mvc action 请求参数
		ParamUtil.injectParam(this.context.getQueryParamMap(), paramObj);

		return paramObj;
	}

	private PathParam getPathParamAnn(Annotation[] anns) {
		for (Annotation a : anns) {
			if (a == null)
				continue;

			if (!a.annotationType().isAssignableFrom(PathParam.class))
				continue;

			return (PathParam) a;
		}

		return null;
	}

	private QueryParam getQueryParamAnn(Annotation[] anns) {
		for (Annotation a : anns) {
			if (a == null)
				continue;

			if (!a.annotationType().isAssignableFrom(QueryParam.class))
				continue;

			return (QueryParam) a;
		}

		return null;
	}

	private void handleResult() throws Exception {

		this.exeActionLog();

		if (retn == null)
			return;
		String re = null;

		String mimeType = null;
		List<String> produces = this.context.getMvcBean().getProduces();
		if (produces != null && produces.size() > 0)
			for (String produce : produces) {
				mimeType = produce;
				break;
			}

		if (!String.class.isAssignableFrom(retn.getClass())) {
			if (this.context.getWriter() == null)
				this.context.setWriter(this.context.getResponse().getWriter());

			if (MIMEType.JSON.equals(mimeType)
					|| "json".equalsIgnoreCase(mimeType)) {
				this.context.getWriter().print(JsonConverter.convert(retn));
				this.context.getResponse().setContentType(MIMEType.JSON);
			} else {
				this.context.getWriter().print("暂时不支持JSON以外的视图渲染技术");
			}
			this.context.getWriter().flush();
			return;
		}

		re = String.valueOf(retn);

		// String contextPath = this.context.getRequest().getContextPath();
		String baseUrl = (String) this.context.getServletContext()
				.getAttribute(MVCConfigConstant.BASE_URL_KEY);
		List<ResultConfigBean> results = this.context.getMvcBean().getResult();
		if (results == null || results.size() == 0) {

			// 客户端重定向
			if (re.startsWith("redirect:")) {
				String url = re.substring("redirect:".length());
				String location = url;

				this.context.getResponse().sendRedirect(location);
			} else if (re.startsWith("action:")) {
				// ACTION 重定向
				String path = re.substring("action:".length());
				this.context.getResponse().sendRedirect(baseUrl + path);

			} else if (re.startsWith("out:") || re.trim().length() == 0) {
				String location = re.substring("out:".length());
				this.context.getWriter().print(location);
				this.context.getWriter().flush();
			} else if (re.startsWith("forward:")) {
				String location = re.substring("forward:".length());
				HttpServletRequest request = this.context.getRequest();
				request.setAttribute(MVCConfigConstant.REQ_PARAM_MAP_NAME,
						this.context.getQueryParamMap());
				// 服务端跳转
				request.getRequestDispatcher(
						MVCConfigConstant.FORWARD_BASE_PATH + location).forward(
						request, this.context.getResponse());
			} else {
				this.context.getWriter().print(retn);
				this.context.getWriter().flush();
			}
		} else {

			for (ResultConfigBean r : results) {
				if (!"_props_".equals(r.getName()) && !r.getName().equals(re)
						&& !"".equals(re))
					continue;

				String type = r.getType();
				String location = r.getLocation();
				if (MVCConfigConstant.REDIRECT_TYPE.equalsIgnoreCase(type)) {
					this.context.getResponse().sendRedirect(location);
				} else if (MVCConfigConstant.FORWARD_TYPE
						.equalsIgnoreCase(type)) {
					HttpServletRequest request = this.context.getRequest();
					request.setAttribute(MVCConfigConstant.REQ_PARAM_MAP_NAME,
							this.context.getQueryParamMap());
					// 服务端跳转
					request.getRequestDispatcher(
							MVCConfigConstant.FORWARD_BASE_PATH + location)
							.forward(request, this.context.getResponse());
				} else if (re != null && re.startsWith("action:")) {
					// ACTION 重定向
					String path = re.substring("action:".length());
					this.context.getResponse().sendRedirect(baseUrl + path);

				} else if (MVCConfigConstant.OUT_TYPE.equalsIgnoreCase(type)
						|| location.trim().length() == 0) {
					this.context.getWriter().print(location);
					this.context.getWriter().flush();
				}
			}
		}
	}

	/**
	 * 执行Action
	 * 
	 * @param methodName
	 * @return
	 * @throws Exception
	 */
	private Object excuteMethod(String methodName) throws Exception {
		// 要执行的Action方法
		Method m = null;

		// 拿到所有方法
		Method[] methods = ru.getMethods(methodName);
		if (methods == null || methods.length == 0)
			return null;

		m = this.getFirstMethd(methods);
		if (m == null)
			return null;

		Class<?>[] paramTypes = m.getParameterTypes();
		// 无参数运行方法
		if (paramTypes == null || paramTypes.length == 0)
			return m.invoke(actionObject);

		Annotation[][] paramAnns = m.getParameterAnnotations();
		// 拿到方法所需要的参数
		Object[] params = assemParams(paramTypes, paramAnns);

		// 带参数运行方法
		return m.invoke(actionObject, params);
	}

	private void injectActionCxt2Pojo(Object pojo) throws Exception {
		ReflectUtil ru = new ReflectUtil(pojo);
		HttpServletRequest req = this.context.getRequest();
		HttpServletResponse res = this.context.getResponse();
		PrintWriter out = this.context.getWriter();
		// ServletOutputStream sos = this.context.getOut();
		HttpSession session = this.context.getSession();
		ActionProp actionProp = this.context.getActionProp();
		QueryParams queryParams = this.context.getQueryParams();
		for (String n : ru.getFieldsName()) {
			Method m = ru.getSetter(n);
			if (m == null)
				continue;

			Class<?> clazz = m.getParameterTypes()[0];
			if (HttpServletRequest.class.isAssignableFrom(clazz)) {
				m.invoke(pojo, req);
			} else if (HttpServletResponse.class.isAssignableFrom(clazz)) {
				m.invoke(pojo, res);
			} else if (PrintWriter.class.isAssignableFrom(clazz)) {
				m.invoke(pojo, out);
			} else if (ServletOutputStream.class.isAssignableFrom(clazz)) {
				m.invoke(pojo, this.context.getOut());
			} else if (HttpSession.class.isAssignableFrom(clazz)) {
				m.invoke(pojo, session);
			} else if (ActionProp.class.isAssignableFrom(clazz)) {
				if (actionProp == null)
					actionProp = new ActionProp(clazz.getName());

				this.context.setActionProp(actionProp);
				m.invoke(pojo, actionProp);
			} else if (QueryParams.class.isAssignableFrom(clazz)) {
				m.invoke(pojo, queryParams);
			}
		}
	}

	/**
	 * 执行Action
	 * 
	 * @throws Exception
	 */
	public void execute() throws Exception {

		// 验证参数
		if (!this.handleValidator()) {
			return;
		}

		// 实例化pojo
		initPojo();

		// IOC注入对象到pojo中
		injectIocBean();

		// 注入框架mvc action 上下文环境
		this.injectActionCxt2Pojo(actionObject);

		// 注入mvc action 请求参数
		ParamUtil.injectParam(this.context.getQueryParamMap(), actionObject);

		String methodName = this.context.getMvcBean().getMethod();

		if (methodName != null && methodName.length() > 0)
			// 执行方法
			retn = excuteMethod(methodName);
		else if (IAction.class.isAssignableFrom(clazz)) {
			// struts2风格
			IAction action = (IAction) actionObject;
			action.init(this.context);
			retn = action.execute();
		}

		InterExecution after_interExe = new InterExecution("after",
				this.context);// 7.后置拦截器
		if (after_interExe.findAndExecuteInter()) {
			after_interExe.showErr();
			return;
		}

		// 对Action执行返回结果的处理
		this.handleResult();

	}
}
